//
//  Copyright (C) 2013-2025  Nick Gasson
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include "util.h"
#include "array.h"
#include "cov/cov-api.h"
#include "cov/cov-data.h"
#include "cov/cov-priv.h"
#include "cov/cov-structs.h"
#include "hash.h"
#include "ident.h"
#include "lib.h"
#include "object.h"
#include "option.h"
#include "tree.h"
#include "psl/psl-node.h"
#include "type.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <libgen.h>
#include <inttypes.h>

//#define COVER_DEBUG_EMIT
//#define COVER_DEBUG_SCOPE
//#define COVER_DEBUG_MERGE

typedef enum {
   CTRL_PUSH_SCOPE,
   CTRL_POP_SCOPE,
   CTRL_END_OF_FILE,
   CTRL_PUSH_UNIT,
} cov_control_t;

static const struct {
   const char *name;
   uint32_t   flag;
} bin_map[] = {
   { "BIN_TRUE",       COV_FLAG_TRUE},
   { "BIN_FALSE",      COV_FLAG_FALSE},
   { "BIN_CHOICE",     COV_FLAG_CHOICE},
   { "BIN_0_0",        COV_FLAG_00},
   { "BIN_0_1",        COV_FLAG_01},
   { "BIN_1_0",        COV_FLAG_10},
   { "BIN_1_1",        COV_FLAG_11},
   { "BIN_0_TO_1",     COV_FLAG_TOGGLE_TO_1},
   { "BIN_1_TO_0",     COV_FLAG_TOGGLE_TO_0},
};

#define COVER_FILE_MAGIC   0x6e636462   // ASCII "ncdb"
#define COVER_FILE_VERSION 4

static inline unsigned get_next_tag(cover_block_t *b)
{
   assert(b->data == NULL);
   return b->next_tag++;
}

static bool cover_is_branch(tree_t branch)
{
   return tree_kind(branch) == T_CHOICE || tree_kind(branch) == T_COND_STMT;
}

static cover_src_t get_cover_source(cover_item_kind_t kind, object_t *obj)
{
   if (obj == NULL)
      return COV_SRC_UNKNOWN;

   tree_t t = tree_from_object(obj);
   if (t != NULL) {
      switch (kind) {
      case COV_ITEM_STMT:
         switch (tree_kind(t)) {
         case T_ASSERT:
            return tree_has_value(t) ? COV_SRC_ASSERT : COV_SRC_REPORT;
         case T_WAIT:
            return COV_SRC_WAIT;
         case T_FOR:
         case T_WHILE:
            return COV_SRC_LOOP_STMT;
         case T_SIGNAL_ASSIGN:
            return COV_SRC_SIGNAL_ASSIGN;
         case T_VAR_ASSIGN:
            return COV_SRC_VAR_ASSIGN;
         case T_IF:
            return COV_SRC_IF_STMT;
         default:
            return COV_SRC_STATEMENT;
         }
      case COV_ITEM_BRANCH:
         switch (tree_kind(t)) {
         case T_COND_STMT:
         case T_COND_ASSIGN:
            return COV_SRC_IF_CONDITION;
         case T_CHOICE:
            return COV_SRC_CASE_CHOICE;
         case T_WHILE:
         case T_EXIT:
         case T_NEXT:
            return COV_SRC_LOOP_CONTROL;
         default:
            return COV_SRC_CONDITION;
         }
      default:
         return COV_SRC_UNKNOWN;
      }
   }

   psl_node_t p = psl_from_object(obj);
   if (p != NULL) {
      switch (kind) {
      case COV_ITEM_FUNCTIONAL:
         return COV_SRC_PSL_COVER;
      default:
         return COV_SRC_UNKNOWN;
      }
   }

   return COV_SRC_UNKNOWN;
}

const loc_t get_cover_loc(cover_item_kind_t kind, object_t *obj)
{
   if (obj == NULL)
      return LOC_INVALID;

   tree_t t = tree_from_object(obj);
   if (t != NULL) {
      // Refer location of test condition instead of branch statement to
      // get accurate test condition location in the coverage report
      if (kind == COV_ITEM_BRANCH && tree_kind(t) != T_CHOICE)
         return *tree_loc(tree_value(t));
   }

   return obj->loc;
}

static int32_t cover_add_item(cover_data_t *data, cover_scope_t *cs,
                              object_t *obj, ident_t suffix,
                              cover_item_kind_t kind, uint32_t flags,
                              int consecutive)
{
   // Everything creates scope, so name of current item is already given
   // by scope in hierarchy.
   ident_t hier = cs->hier;
   if (suffix)
      hier = ident_prefix(hier, suffix, '\0');

   ident_t func_name = NULL;
   loc_t loc_lhs = LOC_INVALID, loc_rhs = LOC_INVALID;

   // Expression items do not nest scope, expression name must be created
   if (kind == COV_ITEM_EXPRESSION) {
      char buf[16];
      checked_sprintf(buf, sizeof(buf), "_E%d", cs->expression_label);
      hier = ident_prefix(hier, ident_new(buf), '.');
      cs->expression_label++;

      tree_t t = tree_from_object(obj);
      assert(t != NULL);

      // Query LHS/RHS operand locations of binary expressions
      if (flags & COVER_FLAGS_LHS_RHS_BINS) {
         assert(tree_params(t) > 1);
         loc_lhs = *tree_loc(tree_param(t, 0));
         loc_rhs = *tree_loc(tree_param(t, 1));
      }

      func_name = tree_ident(t);
   }

   // Append BIN name to the cover item
   if (kind == COV_ITEM_BRANCH ||kind == COV_ITEM_TOGGLE || kind == COV_ITEM_EXPRESSION)
      hier = ident_prefix(hier, ident_new(cover_bmask_to_bin_str(flags)), '.');

   int64_t metadata = 0;
   if (kind == COV_ITEM_TOGGLE)
      metadata = cs->sig_pos;

   loc_t loc = get_cover_loc(kind, obj);

   cover_item_t new = {
      .kind          = kind,
      .tag           = get_next_tag(cs->block),
      .data          = 0,
      .flags         = flags,
      .loc           = loc,
      .loc_lhs       = loc_lhs,
      .loc_rhs       = loc_rhs,
      .hier          = hier,
      .func_name     = func_name,
      .consecutive   = consecutive,
      .atleast       = data->threshold,
      .metadata      = metadata,
      .n_ranges      = 0,
      .ranges        = NULL,
      .source        = get_cover_source(kind, obj),
   };

#ifdef COVER_DEBUG_EMIT
   printf("Item: %s\n", istr(hier));
   printf("    Kind:          %s\n", cover_item_kind_str(kind));
   printf("    Flags:         0x%x\n", flags);
   printf("    Consecutive:   %d\n", consecutive);
   printf("    Metadata:      %d\n", metadata);
   printf("    Function name: %s\n", istr(func_name));
   printf("    First line:    %d\n", loc.first_line);
   printf("    First column:  %d\n", loc.first_column);
   printf("    Line delta:    %d\n", loc.line_delta);
   printf("    Column delta:  %d\n", loc.column_delta);
   printf("\n\n");
#endif

   APUSH(cs->items, new);

   return cs->items.count - 1;
}

///////////////////////////////////////////////////////////////////////////////
// Toggle coverage item emit
///////////////////////////////////////////////////////////////////////////////

static bool cover_is_toggle_first(tree_t decl)
{
   const tree_kind_t kind = tree_kind(decl);
   return kind == T_SIGNAL_DECL || kind == T_PORT_DECL;
}

static bool cover_skip_array_toggle(cover_data_t *data, int a_size)
{
   assert (data);

   // Array is equal to or than configured limit
   if (data->array_limit != 0 && a_size >= data->array_limit)
      return true;

   // Array is multi-dimensional or nested
   if ((!cover_enabled(data, COVER_MASK_TOGGLE_INCLUDE_MEMS)) && data->array_depth > 0)
      return true;

   return false;
}

static int32_t cover_add_toggle_items_one_bit(cover_data_t *data,
                                              cover_scope_t *cs,
                                              object_t *obj,
                                              ident_t suffix,
                                              unsigned int flags)
{
   assert (obj != NULL);

   int32_t first_item_index = cover_add_item(data, cs, obj, suffix,
                                             COV_ITEM_TOGGLE,
                                             flags | COV_FLAG_TOGGLE_TO_1, 2);
   cover_add_item(data, cs, obj, suffix, COV_ITEM_TOGGLE,
                  flags | COV_FLAG_TOGGLE_TO_0, 1);

   return first_item_index;
}

static int32_t cover_add_toggle_items(cover_data_t *data, cover_scope_t *cs,
                                      type_t type, object_t *obj,
                                      ident_t prefix, int curr_dim)
{
   assert (data != NULL);

   type_t root = type;

   // Gets well known type for scalar and vectorized version of
   // standard types (std_[u]logic[_vector], signed, unsigned)
   while (type_base_kind(root) == T_ARRAY)
      root = type_elem(root);
   root = type_base_recur(root);

   well_known_t known = is_well_known(type_ident(root));
   if (known != W_IEEE_ULOGIC && known != W_IEEE_ULOGIC_VECTOR)
      return -1;

   unsigned int flags = 0;
   if (tree_kind(tree_from_object(obj)) == T_SIGNAL_DECL)
      flags |= COV_FLAG_TOGGLE_SIGNAL;
   else
      flags |= COV_FLAG_TOGGLE_PORT;

   if (type_is_scalar(type))
      return cover_add_toggle_items_one_bit(data, cs, obj, NULL, flags);
   else if (type_is_unconstrained(type))
      return -1;   // Not yet supported

   int t_dims = dimension_of(type);
   tree_t r = range_of(type, t_dims - curr_dim);
   int64_t low, high;

   if (!folded_bounds(r, &low, &high))
      return -1;

   if (high < low)
      return -1;

   if (cover_skip_array_toggle(data, high - low + 1))
      return -1;

   int64_t first, last, i;
   int32_t first_item_index = -1;
   int inc;

   data->array_depth++;
#ifdef COVER_DEBUG_SCOPE
   printf("Added dimension: %d\n", data->array_depth);
#endif

   switch (tree_subkind(r)) {
   case RANGE_DOWNTO:
      i = high;
      first = high;
      last = low;
      inc = -1;
      break;
   case RANGE_TO:
      i = low;
      first = low;
      last = high;
      inc = +1;
      break;
   default:
      fatal_trace("invalid subkind for range: %d", tree_subkind(r));
   }

   while (1) {
      char arr_index[16];
      int32_t tmp = -1;
      checked_sprintf(arr_index, sizeof(arr_index), "(%"PRIi64")", i);
      ident_t arr_suffix =
         ident_prefix(prefix, ident_new(arr_index), '\0');

      // On lowest dimension walk through elements, if elements
      // are arrays, then start new (nested) recursion.
      if (curr_dim == 1) {
         type_t e_type = type_elem(type);
         if (type_is_array(e_type))
            tmp = cover_add_toggle_items(data, cs, e_type, obj, arr_suffix,
                                         dimension_of(e_type));
         else
            tmp = cover_add_toggle_items_one_bit(data, cs, obj,
                                                 arr_suffix, flags);
      }
      else   // Recurse to lower dimension
         tmp = cover_add_toggle_items(data, cs, type, obj, arr_suffix,
                                      curr_dim - 1);

      if (i == first)
         first_item_index = tmp;
      if (i == last)
         break;

      i += inc;
   }

   assert(data->array_depth > 0);
   data->array_depth--;
#ifdef COVER_DEBUG_SCOPE
   printf("Subtracted dimension: %d\n", data->array_depth);
#endif

   return first_item_index;
}

///////////////////////////////////////////////////////////////////////////////
// Branch coverage item emit
///////////////////////////////////////////////////////////////////////////////

static int32_t cover_add_branch_items_for(cover_data_t *data, cover_scope_t *cs,
                                          object_t *obj)
{
   tree_t b = tree_from_object(obj);
   int first_item_index = -1;

   // Case choice
   if (tree_kind(b) == T_CHOICE)
      first_item_index = cover_add_item(data, cs, obj, NULL, COV_ITEM_BRANCH,
                                        COV_FLAG_CHOICE, 1);

   // If-else
   else {
      first_item_index = cover_add_item(data, cs, obj, NULL, COV_ITEM_BRANCH,
                                        COV_FLAG_TRUE, 2);
      cover_add_item(data, cs, obj, NULL, COV_ITEM_BRANCH, COV_FLAG_FALSE, 1);
   }

   return first_item_index;
}

///////////////////////////////////////////////////////////////////////////////
// FSM state coverage item emit
///////////////////////////////////////////////////////////////////////////////

static bool cover_skip_type_state(cover_data_t *data, type_t type)
{
   if (!type_is_enum(type))
      return true;

   // Ignore enums from built-in libraries
   ident_t full_name = type_ident(type);
   if (ident_starts_with(full_name, well_known(W_STD)) ||
       ident_starts_with(full_name, well_known(W_IEEE)) ||
       ident_starts_with(full_name, well_known(W_NVC)) ||
       ident_starts_with(full_name, well_known(W_VITAL)))
      return true;

   ident_t name = ident_rfrom(full_name, '.');
   cover_spec_t *spc = data->spec;

   // Type should be recognized as FSM
   if (spc) {
      for (int i = 0; i < spc->fsm_type_include.count; i++)
         if (ident_glob(name, AGET(spc->fsm_type_include, i), -1)) {
#ifdef COVER_DEBUG_EMIT
            printf("Cover emit: True, fsm-type (Type: %s, Pattern: %s)\n",
                  istr(name), AGET(spc->fsm_type_include, i));
#endif
            return false;
         }
   }

   // By default enums should not included
   if (data->mask & COVER_MASK_FSM_NO_DEFAULT_ENUMS)
      return true;

   // Type should not be included
   if (spc) {
      for (int i = 0; i < spc->fsm_type_exclude.count; i++)
         if (ident_glob(name, AGET(spc->fsm_type_exclude, i), -1)) {
#ifdef COVER_DEBUG_EMIT
            printf("Cover emit: False, fsm-type (Type: %s, Pattern: %s)\n",
                   istr(name), AGET(spc->fsm_type_exclude, i));
#endif
            return true;
         }
     }

   return false;
}

static int32_t cover_add_state_items_for(cover_data_t *data, cover_scope_t *cs,
                                         object_t *obj)
{
   type_t type = tree_type(tree_from_object(obj));

   if (cover_skip_type_state(data, type))
      return -1;

   int64_t low, high;
   if (!folded_bounds(range_of(type, 0), &low, &high))
      return -1;

   // Add single coverage item per enum literal. This is to track
   // literal string in the identifier of the coverage item.
   type_t base = type_base_recur(type);
   assert(type_is_enum(base));
   ident_t itype = type_ident(type);
   int64_t first_item_index = 0;

   for (int64_t i = low; i <= high; i++) {
      ident_t literal = tree_ident(type_enum_literal(base, i));
      ident_t suffix = ident_prefix(ident_new(".BIN_STATE"), literal, '.');
      int32_t curr_item_index =
         cover_add_item(data, cs, obj, suffix, COV_ITEM_STATE,
                        COV_FLAG_STATE, ((int)(high - i)) + 1);

      // For FSM State coverage, "func_name" stores name of the FSM Enum type
      // TODO: Move this logic into "cover_add_item"
      cover_item_t *item = AREF(cs->items, curr_item_index);
      item->func_name = ident_rfrom(itype, '.');
      if (i == low) {
         item->metadata = low;
         first_item_index = curr_item_index;
      }
   }

   return first_item_index;
}

///////////////////////////////////////////////////////////////////////////////
// Expression coverage item emit
///////////////////////////////////////////////////////////////////////////////

static int32_t cover_add_expression_xor_items(cover_data_t *data,
                                              cover_scope_t *cs, object_t *obj,
                                              uint32_t flags)
{
   int first_item_index = cover_add_item(data, cs, obj, NULL,
                                         COV_ITEM_EXPRESSION,
                                         flags | COV_FLAG_00, 4);

   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_01, 3);
   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_10, 2);
   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_11, 1);

   return first_item_index;
}

static int32_t cover_add_expression_and_items(cover_data_t *data,
                                              cover_scope_t *cs,
                                              object_t *obj, uint32_t flags)
{
   int first_item_index = cover_add_item(data, cs, obj, NULL,
                                         COV_ITEM_EXPRESSION,
                                         flags | COV_FLAG_01, 3);

   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_10, 2);
   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_11, 1);

   return first_item_index;
}

static int32_t cover_add_expression_or_items(cover_data_t *data,
                                             cover_scope_t *cs,
                                             object_t *obj, uint32_t flags)
{
   int first_item_index = cover_add_item(data, cs, obj, NULL,
                                         COV_ITEM_EXPRESSION,
                                         flags | COV_FLAG_00, 3);

   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_01, 2);
   cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                  flags | COV_FLAG_10, 1);

   return first_item_index;
}

static int32_t cover_add_builtin_expression_items(cover_data_t *data,
                                                  cover_scope_t *cs,
                                                  object_t *obj,
                                                  subprogram_kind_t builtin)
{
   // Handle expression result True or False
   switch (builtin) {
   case S_SCALAR_EQ:
   case S_SCALAR_NEQ:
   case S_SCALAR_LT:
   case S_SCALAR_GT:
   case S_SCALAR_LE:
   case S_SCALAR_GE:
   case S_SCALAR_NOT:
   {
      int first_item_index = cover_add_item(data, cs, obj, NULL,
                                            COV_ITEM_EXPRESSION,
                                            COV_FLAG_FALSE, 2);
      cover_add_item(data, cs, obj, NULL, COV_ITEM_EXPRESSION,
                     COV_FLAG_TRUE, 1);

      return first_item_index;
   }

   case S_SCALAR_XOR:
   case S_SCALAR_XNOR:
      return cover_add_expression_xor_items(data, cs, obj, 0);

   case S_SCALAR_OR:
   case S_SCALAR_NOR:
      return cover_add_expression_or_items(data, cs, obj, 0);

   case S_SCALAR_AND:
   case S_SCALAR_NAND:
      return cover_add_expression_and_items(data, cs, obj, 0);

   default:
      fatal_trace("unhandled subprogram kind %d in cover_add_builtin_expression_items",
                  builtin);
   }

   return -1;
}

static int32_t cover_add_logic_expression_items(cover_data_t *data,
                                                cover_scope_t *cs,
                                                object_t *obj)
{
   tree_t fcall = tree_from_object(obj);
   tree_t decl = tree_ref(fcall);

   if (tree_kind(decl) != T_FUNC_DECL)
      return -1;

   tree_t container = tree_container(decl);
   if (is_well_known(tree_ident(container)) != W_IEEE_1164)
      return -1;

   // Only binary expressions
   if (tree_params(fcall) != 2)
      return -1;

   // Skip arrays -> Matches behavior of VCS and Modelsim
   if (type_is_array(type_param(tree_type(decl), 0)) ||
       type_is_array(type_param(tree_type(decl), 1)))
      return -1;

   ident_t full_name = tree_ident2(decl);

   // Emit items for each of AND, NAND, OR, NOR, XOR, XNOR
   if (ident_starts_with(full_name, well_known(W_IEEE_1164_AND)) ||
       ident_starts_with(full_name, well_known(W_IEEE_1164_NAND)))
      return cover_add_expression_and_items(data, cs, obj,
                                            COV_FLAG_EXPR_STD_LOGIC);

   if (ident_starts_with(full_name, well_known(W_IEEE_1164_OR)) ||
       ident_starts_with(full_name, well_known(W_IEEE_1164_NOR)))
      return cover_add_expression_or_items(data, cs, obj,
                                           COV_FLAG_EXPR_STD_LOGIC);

   if (ident_starts_with(full_name, well_known(W_IEEE_1164_XOR)) ||
       ident_starts_with(full_name, well_known(W_IEEE_1164_XNOR)))
      return cover_add_expression_xor_items(data, cs, obj,
                                            COV_FLAG_EXPR_STD_LOGIC);

   return -1;
}

///////////////////////////////////////////////////////////////////////////////
// Lower EMIT API
///////////////////////////////////////////////////////////////////////////////

cover_item_t *cover_add_items_for(cover_data_t *data, cover_scope_t *cs,
                                  object_t *obj, cover_item_kind_t kind)
{
   assert(data != NULL);

   if (!cs->emit)
      return NULL;

   const loc_t loc = get_cover_loc(kind, obj);

   // Multiple scopes may be emitted from a single file for generate
   // statements, blocks, etc.
   for (cover_scope_t *ignore_scope = cs; ignore_scope->parent;
        ignore_scope = ignore_scope->parent) {
      if (cover_block_kind(ignore_scope) == CBLOCK_NONE)
         continue;
      else if (ignore_scope->loc.file_ref != loc.file_ref)
         break;

      for (int i = 0; i < ignore_scope->ignore_lines.count; i++) {
         line_range_t *lr = &(ignore_scope->ignore_lines.items[i]);
         if (loc.first_line > lr->start && loc.first_line <= lr->end)
            return NULL;
      }
   }

   // Emit items based on item kind
   int32_t first_item_index = -1;

   switch (kind) {
   case COV_ITEM_STMT:
      first_item_index = cover_add_item(data, cs, obj, NULL,
                                        COV_ITEM_STMT, 0, 1);
      break;

   case COV_ITEM_BRANCH:
      first_item_index = cover_add_branch_items_for(data, cs, obj);
      break;

   case COV_ITEM_STATE:
      first_item_index = cover_add_state_items_for(data, cs, obj);
      break;

   case COV_ITEM_FUNCTIONAL:
      first_item_index = cover_add_item(data, cs, obj, NULL,
                                        COV_ITEM_FUNCTIONAL, 0, 1);
      break;

   case COV_ITEM_TOGGLE:
      {
         tree_t tree = tree_from_object(obj);
         type_t type = tree_type(tree);
         const int ndims = dimension_of(type);
         first_item_index =
            cover_add_toggle_items(data, cs, type, obj, NULL, ndims);
      }
      break;
   case COV_ITEM_EXPRESSION:
   {
      tree_t tree = tree_from_object(obj);
      if (tree_kind(tree) == T_PROT_FCALL)
         break;

      assert(tree_kind(tree) == T_FCALL);

      // Choose if to emit for built-in or "std_logic"
      tree_t decl = tree_ref(tree);
      const subprogram_kind_t kind = tree_subkind(decl);

      if (is_open_coded_builtin(kind))
         first_item_index =
            cover_add_builtin_expression_items(data, cs, obj, kind);
      else
         first_item_index = cover_add_logic_expression_items(data, cs, obj);

      break;
   }

   default:
      fatal("unsupported type of code coverage: %d at 'cover_add_items_for' !", kind);
   }

   return (first_item_index == -1) ? NULL : AREF(cs->items, first_item_index);
}

///////////////////////////////////////////////////////////////////////////////
// Coverage data write/read to covdb, covdb merging and coverage scope handling
///////////////////////////////////////////////////////////////////////////////

void cover_merge_one_item(cover_item_t *item, int32_t data)
{
   switch (item->kind) {
   case COV_ITEM_STMT:
   case COV_ITEM_FUNCTIONAL:
   case COV_ITEM_BRANCH:
   case COV_ITEM_STATE:
   case COV_ITEM_EXPRESSION:
      item->data = saturate_add(item->data, data);
      break;

   // Highest bit of run-time data for COV_ITEM_TOGGLE is used to track
   // unreachability due to being constant driven. If multiple designs
   // like this are merged, the resulting value would underflow and this
   // information would be lost. Similarly, if different designs were
   // merged, where one was unreachable due to being constant driven and
   // the other was driven. So, If the unreachability is detected, enforce
   // its propagation further to the merged database
   case COV_ITEM_TOGGLE:

      if ((item->data & COV_FLAG_UNREACHABLE) || (data & COV_FLAG_UNREACHABLE))
         item->data = COV_FLAG_UNREACHABLE;
      else
         item->data = saturate_add(item->data, data);
      break;

   default:
      break;
   }
}

static void cover_update_counts(cover_scope_t *s)
{
   if (s->block != NULL && s->block->data != NULL) {
      for (int i = 0; i < s->items.count; i++) {
         cover_item_t *item = &(s->items.items[i]);
         cover_merge_one_item(item, s->block->data[item->tag]);
      }
   }

   for (int i = 0; i < s->children.count; i++)
      cover_update_counts(s->children.items[i]);
}

static void cover_write_scope(cover_scope_t *s, fbuf_t *f,
                              ident_wr_ctx_t ident_ctx, loc_wr_ctx_t *loc_ctx)
{
   if (s->block != NULL && s == s->block->self) {
      write_u8(CTRL_PUSH_UNIT, f);

      ident_write(s->block->name, ident_ctx);
      ident_write(s->block->block_name, ident_ctx);
      fbuf_put_uint(f, s->block->kind);
      fbuf_put_uint(f, s->block->next_tag);
   }
   else
      write_u8(CTRL_PUSH_SCOPE, f);

   ident_write(s->name, ident_ctx);
   ident_write(s->hier, ident_ctx);
   loc_write(&s->loc, loc_ctx);

   fbuf_put_uint(f, s->items.count);
   for (int i = 0; i < s->items.count; i++) {
      cover_item_t *item = &(s->items.items[i]);

      fbuf_put_uint(f, item->kind);
      fbuf_put_uint(f, item->tag);
      fbuf_put_uint(f, item->data);
      fbuf_put_uint(f, item->flags);
      fbuf_put_uint(f, item->source);
      fbuf_put_uint(f, item->consecutive);
      fbuf_put_uint(f, item->atleast);
      fbuf_put_uint(f, item->n_ranges);
      fbuf_put_uint(f, item->metadata);

      for (int i = 0; i < item->n_ranges; i++) {
         fbuf_put_uint(f, item->ranges[i].min);
         fbuf_put_uint(f, item->ranges[i].max);
      }

      loc_write(&(item->loc), loc_ctx);
      if (item->flags & COVER_FLAGS_LHS_RHS_BINS) {
         loc_write(&(item->loc_lhs), loc_ctx);
         loc_write(&(item->loc_rhs), loc_ctx);
      }

      ident_write(item->hier, ident_ctx);
      if (item->kind == COV_ITEM_EXPRESSION ||
          item->kind == COV_ITEM_STATE ||
          item->kind == COV_ITEM_FUNCTIONAL)
         ident_write(item->func_name, ident_ctx);
   }

   for (int i = 0; i < s->children.count; i++)
      cover_write_scope(s->children.items[i], f, ident_ctx, loc_ctx);

   write_u8(CTRL_POP_SCOPE, f);
}

LCOV_EXCL_START
static void cover_debug_dump(cover_scope_t *s, int indent)
{
   color_printf("%*s$!blue$%s$$", indent, "", istr(s->name));
   if (cover_is_hier(s))
      printf(" : %s", istr(s->block->block_name));
   color_printf("\n");

   for (int i = 0; i < s->items.count; i++) {
      cover_item_t *item = &(s->items.items[i]);

      char *path LOCAL = xstrdup(loc_file_str(&item->loc));
      printf("%*s%d: %s %s %s:%d => %x\n", indent + 2, "", item->tag,
             cover_item_kind_str(item->kind), istr(item->hier),
             basename(path), item->loc.first_line, item->data);
   }

   for (int i = 0; i < s->children.count; i++)
      cover_debug_dump(s->children.items[i], indent + 2);
}
LCOV_EXCL_STOP

void cover_dump_items(cover_data_t *data, fbuf_t *f, cover_dump_t dt)
{
   if (dt == COV_DUMP_RUNTIME)
      cover_update_counts(data->root_scope);

   if (opt_get_int(OPT_COVER_VERBOSE))
      cover_debug_dump(data->root_scope, 0);

   write_u32(COVER_FILE_MAGIC, f);
   fbuf_put_uint(f, COVER_FILE_VERSION);

   fbuf_put_uint(f, data->mask);
   fbuf_put_uint(f, data->array_limit);

   loc_wr_ctx_t *loc_wr = loc_write_begin(f);
   ident_wr_ctx_t ident_ctx = ident_write_begin(f);

   cover_write_scope(data->root_scope, f, ident_ctx, loc_wr);

   write_u8(CTRL_END_OF_FILE, f);

   loc_write_end(loc_wr);
   ident_write_end(ident_ctx);
}

cover_data_t *cover_data_init(cover_mask_t mask, int array_limit, int threshold)
{
   cover_data_t *data = xcalloc(sizeof(cover_data_t));
   data->mask = mask;
   data->array_limit = array_limit;
   data->threshold = threshold;
   data->blocks = hash_new(16);
   data->pool = pool_new();

   return data;
}

void cover_data_free(cover_data_t *db)
{
#ifdef DEBUG
   size_t alloc, npages;
   pool_stats(db->pool, &alloc, &npages);
   if (npages > 0)
      debugf("coverage database allocated %zu bytes in %zu pages",
             alloc, npages);
#endif

   hash_free(db->blocks);
   pool_free(db->pool);
   free(db);
}

bool cover_enabled(cover_data_t *data, cover_mask_t mask)
{
   return data != NULL && (data->mask & mask);
}

static bool cover_should_emit_scope(cover_data_t *data, cover_scope_t *cs)
{
   cover_spec_t *spc = data->spec;
   if (spc == NULL)
      return true;

   // Block (entity, package instance or block) name
   if (cs->block->block_name != NULL) {
      ident_t ename = ident_until(cs->block->block_name, '-');

      for (int i = 0; i < spc->block_exclude.count; i++) {
         if (ident_glob(ename, AGET(spc->block_exclude, i), -1))
            return false;
      }

      for (int i = 0; i < data->spec->block_include.count; i++) {
         if (ident_glob(ename, AGET(spc->block_include, i), -1))
            return true;
      }
   }

   // Hierarchy
   for (int i = 0; i < spc->hier_exclude.count; i++) {
      if (ident_glob(cs->hier, AGET(spc->hier_exclude, i), -1))
         return false;
   }

   for (int i = 0; i < spc->hier_include.count; i++) {
      if (ident_glob(cs->hier, AGET(spc->hier_include, i), -1))
         return true;
   }

   return false;
}

cover_scope_t *cover_create_block(cover_data_t *data, ident_t qual,
                                  cover_scope_t *parent, tree_t inst,
                                  tree_t unit, ident_t name)
{
   if (data == NULL)
      return NULL;

   cover_block_t *b = pool_calloc(data->pool, sizeof(cover_block_t));
   b->name = qual;

   assert(hash_get(data->blocks, qual) == NULL);
   hash_put(data->blocks, qual, b);

   if (parent == NULL) {
      assert(data->root_scope == NULL);

      parent = data->root_scope = xcalloc(sizeof(cover_scope_t));
      parent->name = parent->hier = lib_name(lib_work());
   }

   ident_t scope_name;
   if (name != NULL)
      scope_name = name;
   else if (tree_kind(inst) == T_INERTIAL)   // Process without label
      scope_name = ident_sprintf("_S%u", parent->stmt_label++);
   else if (tree_has_ident(inst))
      scope_name = tree_ident(inst);
   else
      scope_name = ident_sprintf("_S%u", parent->stmt_label++);

   cover_scope_t *s = pool_calloc(data->pool, sizeof(cover_scope_t));
   s->name    = scope_name;
   s->parent  = parent;
   s->block   = b;
   s->sig_pos = parent->sig_pos;
   s->loc     = *tree_loc(unit);
   s->hier    = ident_prefix(parent->hier, scope_name, '.');
   s->emit    = cover_should_emit_scope(data, s);

   switch (tree_kind(inst)) {
   case T_BLOCK:
      if (name == NULL) {
         b->kind = CBLOCK_INSTANCE;
         b->block_name = ident_rfrom(tree_ident(unit), '.');
      }
      else
         b->kind = CBLOCK_USER;   // XXX
      break;
   case T_PROCESS:
   case T_INERTIAL:
      b->kind = CBLOCK_PROCESS;
      b->block_name = parent->block->block_name;
      break;
   case T_PROC_BODY:
   case T_FUNC_BODY:
      b->kind = CBLOCK_SUBPROG;
      break;
   case T_PSL_DIRECT:
      b->kind = CBLOCK_PROPERTY;
      break;
   case T_PACK_INST:
   case T_PACKAGE:
   case T_PACK_BODY:
      b->kind = CBLOCK_PACKAGE;
      b->block_name = tree_ident(unit);
      break;
   default:
      should_not_reach_here();
   }

   APUSH(parent->children, s);
   b->self = s;

   return b->self;
}

cover_scope_t *cover_create_scope(cover_data_t *data, cover_scope_t *parent,
                                  tree_t t, ident_t name)
{
   if (data == NULL)
      return NULL;

   assert(parent != NULL);
   assert(data->root_scope != NULL);

   cover_scope_t *s = pool_calloc(data->pool, sizeof(cover_scope_t));

   if (cover_is_branch(t)) {
      if (tree_kind(t) == T_CHOICE && !tree_has_name(t))
         name = ident_new("_B_OTHERS");
      else
         name = ident_sprintf("_B%u", parent->branch_label++);
   }
   // For toggle coverage, remember the position where its name in
   // the hierarchy starts.
   else if (cover_is_toggle_first(t)) {
      assert(tree_has_ident(t));
      name = tree_ident(t);
      s->sig_pos = ident_len(parent->hier) + 1;
   }
   else if (tree_kind(t) == T_INERTIAL)   // Process without label
      name = ident_sprintf("_S%u", parent->stmt_label++);
   else if (name == NULL && tree_has_ident(t))
      name = tree_ident(t);
   // Consider everything else as statement
   // Expressions do not get scope pushed, so if scope for e.g.
   // T_FCALL is pushed it will be concurent function call -> Label as statement
   else if (name == NULL)
      name = ident_sprintf("_S%u", parent->stmt_label++);

   s->name   = name;
   s->parent = parent;
   s->block  = parent->block;
   s->loc    = *tree_loc(t);
   s->hier   = ident_prefix(parent->hier, name, '.');
   s->emit   = cover_should_emit_scope(data, s);

   if (s->sig_pos == 0)
      s->sig_pos = parent->sig_pos;

   APUSH(parent->children, s);
   return s;
}

static void cover_read_header(fbuf_t *f, cover_data_t *data)
{
   assert(data != NULL);

   if (read_u32(f) != COVER_FILE_MAGIC)
      fatal("%s is not a valid coverage database", fbuf_file_name(f));

   const unsigned version = fbuf_get_uint(f);
   if (version != COVER_FILE_VERSION)
      fatal("coverage database %s format version %d is not the expected %d",
            fbuf_file_name(f), version, COVER_FILE_VERSION);

   data->mask        = fbuf_get_uint(f);
   data->array_limit = fbuf_get_uint(f);
}

static void cover_read_one_item(fbuf_t *f, loc_rd_ctx_t *loc_rd,
                                ident_rd_ctx_t ident_ctx, cover_item_t *item)
{
   item->kind        = fbuf_get_uint(f);
   item->tag         = fbuf_get_uint(f);
   item->data        = fbuf_get_uint(f);
   item->flags       = fbuf_get_uint(f);
   item->source      = fbuf_get_uint(f);
   item->consecutive = fbuf_get_uint(f);
   item->atleast     = fbuf_get_uint(f);
   item->n_ranges    = fbuf_get_uint(f);
   item->metadata    = fbuf_get_uint(f);

   if (item->n_ranges > 0)
      item->ranges = xcalloc_array(item->n_ranges, sizeof(cover_range_t));

   for (int i = 0; i < item->n_ranges; i++) {
      item->ranges[i].min = fbuf_get_uint(f);
      item->ranges[i].max = fbuf_get_uint(f);
   }

   loc_read(&(item->loc), loc_rd);
   if (item->flags & COVER_FLAGS_LHS_RHS_BINS) {
      loc_read(&(item->loc_lhs), loc_rd);
      loc_read(&(item->loc_rhs), loc_rd);
   }

   item->hier = ident_read(ident_ctx);
   if (item->kind == COV_ITEM_EXPRESSION ||
       item->kind == COV_ITEM_STATE ||
       item->kind == COV_ITEM_FUNCTIONAL)
      item->func_name = ident_read(ident_ctx);
}

static cover_scope_t *cover_read_scope(cover_data_t *db, fbuf_t *f,
                                       ident_rd_ctx_t ident_ctx,
                                       loc_rd_ctx_t *loc_ctx,
                                       cover_block_t *b,
                                       cover_scope_t *parent)
{
   cover_scope_t *s = pool_calloc(db->pool, sizeof(cover_scope_t));
   s->name   = ident_read(ident_ctx);
   s->hier   = ident_read(ident_ctx);
   s->block  = b;
   s->parent = parent;

   loc_read(&s->loc, loc_ctx);

   const int nitems = fbuf_get_uint(f);
   for (int i = 0; i < nitems; i++) {
      cover_item_t new;
      cover_read_one_item(f, loc_ctx, ident_ctx, &new);

      APUSH(s->items, new);
   }

   for (;;) {
      const uint8_t ctrl = read_u8(f);
      switch (ctrl) {
      case CTRL_PUSH_UNIT:
         {
            // FIXME: this check is required for merging
            ident_t name = ident_read(ident_ctx);
            cover_block_t *b = hash_get(db->blocks, name);
            if (b == NULL) {
               b = xcalloc(sizeof(cover_block_t));
               b->name = name;
               b->block_name = ident_read(ident_ctx);
               b->kind = fbuf_get_uint(f);
               b->next_tag = fbuf_get_uint(f);
               b->self = cover_read_scope(db, f, ident_ctx, loc_ctx, b, s);

               hash_put(db->blocks, b->name, b);

               APUSH(s->children, b->self);
            }
            else {
               (void)ident_read(ident_ctx);
               (void)fbuf_get_uint(f);
               (void)fbuf_get_uint(f);

               cover_scope_t *child =
                  cover_read_scope(db, f, ident_ctx, loc_ctx, b, s);
               APUSH(s->children, child);
            }
         }
         break;
      case CTRL_PUSH_SCOPE:
         {
            cover_scope_t *child =
               cover_read_scope(db, f, ident_ctx, loc_ctx, b, s);
            APUSH(s->children, child);
         }
         break;
      case CTRL_POP_SCOPE:
         return s;
      default:
         fatal_trace("invalid control word %x in cover db", ctrl);
      }
   }
}

cover_data_t *cover_read_items(fbuf_t *f, uint32_t pre_mask)
{
   cover_data_t *data = xcalloc(sizeof(cover_data_t));
   cover_read_header(f, data);
   data->mask |= pre_mask;
   data->blocks = hash_new(16);
   data->pool = pool_new();

   loc_rd_ctx_t *loc_rd = loc_read_begin(f);
   ident_rd_ctx_t ident_ctx = ident_read_begin(f);

   bool eof = false;
   do {
      const uint8_t ctrl = read_u8(f);
      switch (ctrl) {
      case CTRL_PUSH_SCOPE:
         data->root_scope =
            cover_read_scope(data, f, ident_ctx, loc_rd, NULL, NULL);
         break;
      case CTRL_END_OF_FILE:
         eof = true;
         break;
      default:
         fatal_trace("invalid control word %x in cover db", ctrl);
      }
   } while (!eof);

   ident_read_end(ident_ctx);
   loc_read_end(loc_rd);

   return data;
}

static void cover_merge_scope(cover_scope_t *old_s, cover_scope_t *new_s,
                              merge_mode_t mode)
{
   // Most merged cover scopes have equal items.
   // Start from equal item index in old and new scope instead of iterating
   // for each items.
   // If new scope has extra item in the middle (e.g. due to generic sized array)
   // account for offset. Then there is no reiteration upon added items.
   int n_added = 0;
   for (int i = 0; i < new_s->items.count; i++) {
      cover_item_t *new = AREF(new_s->items, i);
      cover_item_t *old = AREF(old_s->items, i - n_added);

      bool found = false;
      int n_old_visits = 0;

      do {
         if (n_old_visits == old_s->items.count)
            break;

         if ((new->hier == old->hier) && (new->flags == old->flags)) {
            assert(new->kind == old->kind);
#ifdef COVER_DEBUG_MERGE
            printf("Merging coverage item: %s\n", istr(old->hier));
#endif
            cover_merge_one_item(old, new->data);
            found = true;
            break;
         }

         if (old == AREF(old_s->items, old_s->items.count - 1))
            old = AREF(old_s->items, 0);
         else
            old++;

         n_old_visits++;
      } while (true);

      if (!found && mode == MERGE_UNION) {
         APUSH(old_s->items, *new);
         n_added++;
      }
   }

   for (int i = 0; i < new_s->children.count; i++) {
      cover_scope_t *new_c = new_s->children.items[i];
      bool found = false;
      for (int j = 0; j < old_s->children.count; j++) {
         cover_scope_t *old_c = old_s->children.items[j];
         if (new_c->name == old_c->name) {
            cover_merge_scope(old_c, new_c, mode);
            found = true;
            break;
         }
      }

      if (!found && mode == MERGE_UNION)
         APUSH(old_s->children, new_c);
   }
}

void cover_merge_items(fbuf_t *f, cover_data_t *data, merge_mode_t mode)
{
   assert (data != NULL);

   cover_read_header(f, data);

   loc_rd_ctx_t *loc_rd = loc_read_begin(f);
   ident_rd_ctx_t ident_ctx = ident_read_begin(f);

   bool eof = false;
   do {
      const uint8_t ctrl = read_u8(f);
      switch (ctrl) {
      case CTRL_PUSH_SCOPE:
         {
            cover_scope_t *new =
               cover_read_scope(data, f, ident_ctx, loc_rd, NULL, NULL);
            cover_merge_scope(data->root_scope, new, mode);
         }
         break;
      case CTRL_END_OF_FILE:
         eof = true;
         break;
      default:
         fatal_trace("invalid control word %x in cover db", ctrl);
      }
   } while (!eof);

   ident_read_end(ident_ctx);
   loc_read_end(loc_rd);
}

int32_t *cover_get_counters(cover_data_t *db, ident_t name)
{
   if (db == NULL)
      return NULL;

   cover_block_t *b = hash_get(db->blocks, name);
   if (b == NULL || b->next_tag == 0)
      return NULL;

   if (b->data == NULL)
      b->data = pool_calloc(db->pool, b->next_tag *sizeof(int32_t));

   return b->data;
}

cover_scope_t *cover_get_scope(cover_data_t *db, ident_t name)
{
   if (db == NULL)
      return NULL;

   cover_block_t *b = hash_get(db->blocks, name);
   if (b == NULL)
      return NULL;

   return b->self;
}

void cover_bmask_to_bin_list(uint32_t bmask, text_buf_t *tb)
{
   bool empty = true;
   for (int i = 0; i < ARRAY_LEN(bin_map); i++) {
      if (bmask & bin_map[i].flag) {
         if (!empty)
            tb_cat(tb, ", ");
         tb_cat(tb, bin_map[i].name);
         empty = false;
      }
   }
}

uint32_t cover_bin_str_to_bmask(const char *bin)
{
   for (int i = 0; i < ARRAY_LEN(bin_map); i++) {
      if (strcmp(bin, bin_map[i].name) == 0)
         return bin_map[i].flag;
   }

   return 0;
}

const char *cover_bmask_to_bin_str(uint32_t bmask)
{
   // TODO: Smarter way instead of iterating -> Probably OK for such small array
   //       even if called many times!
   for (int i = 0; i < ARRAY_LEN(bin_map); i++)
      if (bmask & bin_map[i].flag)
         return bin_map[i].name;

   should_not_reach_here();
}

const char *cover_item_kind_str(cover_item_kind_t kind)
{
   static const char* item_kind_str[] = {
      "statement",
      "branch",
      "toggle",
      "expression",
      "FSM state",
      "cover point",
   };
   assert(kind < ARRAY_LEN(item_kind_str));
   return item_kind_str[kind];
}

block_kind_t cover_block_kind(cover_scope_t *s)
{
   if (s->block == NULL)
      return CBLOCK_NONE;  // Dummy root scope
   else if (s->block->self == s)
      return s->block->kind;
   else
      return CBLOCK_NONE;
}

bool cover_is_hier(cover_scope_t *s)
{
   switch (cover_block_kind(s)) {
   case CBLOCK_INSTANCE:
   case CBLOCK_PACKAGE:
      assert(s->block->block_name != NULL);
      return true;
   default:
      return false;
   }
}

bool cover_is_leaf(cover_scope_t *s)
{
   for (int i = 0; i < s->children.count; i++) {
      if (cover_is_hier(s->children.items[i]))
         return false;
   }

   return true;
}

bool cover_bin_unreachable(cover_data_t *data, const cover_item_t *item)
{
   if ((data->mask & COVER_MASK_EXCLUDE_UNREACHABLE) == 0)
      return false;

   // Toggle items remember unreachability in run-time data. Must check
   // item kind not to get false unreachability on statement items.
   // Excludes both bins automatically!
   if (item->kind == COV_ITEM_TOGGLE
       && ((item->data & COV_FLAG_UNREACHABLE) != 0))
      return true;

   // Expression items remember unreachability as unreachable mask
   if (item->kind == COV_ITEM_EXPRESSION
       && ((item->flags & COV_FLAG_UNREACHABLE) != 0))
      return true;

   return false;
}
